package com.aek.ebey.sys.security;

import java.io.Serializable;
import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mobile.device.Device;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import com.aek.common.core.config.RedisRepository;
import com.aek.common.core.exception.ExceptionFactory;
import com.aek.ebey.sys.security.model.DeviceRedis;
import com.alibaba.fastjson.JSON;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

@Component
public class JwtTokenUtil implements Serializable
{
    
    private static final long serialVersionUID = -3301605591108950415L;
    
    private static final String CLAIM_KEY_AUDIENCE = "aud";
    
    private static final String CLAIM_KEY_CREATED = "created";
    
    public static final String CLAIM_KEY_DEVICE_ID = "deviceId";
    
    private static final String AUDIENCE_UNKNOWN = "unknown";
    
    private static final String AUDIENCE_WEB = "web";
    
    private static final String AUDIENCE_MOBILE = "mobile";
    
    private static final String AUDIENCE_TABLET = "tablet";
    
    /**
     * Token 类型
     */
    public static final String TOKEN_TYPE_BEARER = "Bearer";
    
    /**
     * 权限缓存前缀
     */
    public static final String REDIS_PREFIX_AUTH = "auth:";
    
    /**
     * 用户信息缓存前缀
     */
    public static final String REDIS_PREFIX_USER = "user-details:";
    
    /**
     * redis repository
     */
    @Autowired
    private RedisRepository redisRepository;
    
    @Value("${jwt.secret}")
    private String secret;
    
    @Value("${jwt.expiration}")
    private Long expiration;
    
    public String getUsernameFromToken(String token)
    {
        String username;
        try
        {
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        }
        catch (Exception e)
        {
            username = null;
        }
        return username;
    }
    
    /**
     * 获取设备
     *
     * @param token
     *            Token
     * @return String
     */
    public String getDeviceIdFromToken(String token)
    {
        Claims claims = getClaimsFromToken(token);
        return claims != null ? claims.get(CLAIM_KEY_DEVICE_ID).toString() : null;
    }
    
    public Date getCreatedDateFromToken(String token)
    {
        Date created;
        try
        {
            final Claims claims = getClaimsFromToken(token);
            created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
        }
        catch (Exception e)
        {
            created = null;
        }
        return created;
    }
    
    public Date getExpirationDateFromToken(String token)
    {
        Date expiration;
        try
        {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        }
        catch (Exception e)
        {
            expiration = null;
        }
        return expiration;
    }
    
    public String getAudienceFromToken(String token)
    {
        String audience;
        try
        {
            final Claims claims = getClaimsFromToken(token);
            audience = (String) claims.get(CLAIM_KEY_AUDIENCE);
        }
        catch (Exception e)
        {
            audience = null;
        }
        return audience;
    }
    
    public Claims getClaimsFromToken(String token)
    {
        Claims claims;
        try
        {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        }
        catch (Exception e)
        {
            claims = null;
        }
        return claims;
    }
    
    private Date generateExpirationDate()
    {
        return new Date(System.currentTimeMillis() + expiration * 1000);
        
    }
    
    private Boolean isTokenExpired(String token)
    {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }
    
    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset)
    {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }
    
    private String generateAudience(Device device)
    {
        String audience = AUDIENCE_UNKNOWN;
        if (device.isNormal())
        {
            audience = AUDIENCE_WEB;
        }
        else if (device.isTablet())
        {
            audience = AUDIENCE_TABLET;
        }
        else if (device.isMobile())
        {
            audience = AUDIENCE_MOBILE;
        }
        return audience;
    }
    
    private Boolean ignoreTokenExpiration(String token)
    {
        String audience = getAudienceFromToken(token);
        return (AUDIENCE_TABLET.equals(audience) || AUDIENCE_MOBILE.equals(audience));
    }
    
    /**
     * 获得用户信息 Json 字符串
     *
     * @param token
     *            Token
     * @return String
     */
    protected String getUserDetailsString(String token)
    {
        final String username = getUsernameFromToken(token);
        final String deviceid = getDeviceIdFromToken(token);
        String key = deviceid + ":" + REDIS_PREFIX_USER + username;
        return redisRepository.get(key);
    }
    
    /**
     * 删除用户信息
     *
     * @param username
     *            用户名
     */
    public void delUserDetails(String username, Long tenantId)
    {
        String key = tenantId + ":" + REDIS_PREFIX_USER + username;
        redisRepository.del(key);
    }
    
    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset)
    {
        final Date created = getCreatedDateFromToken(token);
        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                && (!isTokenExpired(token) || ignoreTokenExpiration(token));
    }
    
    /**
     * 获得用户信息 Json 字符串
     *
     * @param token Token
     * @return String
     */
    public String getAuthUserString(String token)
    {
        final String username = getUsernameFromToken(token);
        final String deviceid = getDeviceIdFromToken(token);
        String key = deviceid + ":" + REDIS_PREFIX_AUTH + username;
        DeviceRedis dr = JSON.parseObject(redisRepository.get(key), DeviceRedis.class);
        if (dr == null)
        {
            throw ExceptionFactory.create("403");
        }
        
        String keyUserDetail = dr.getTenantId() + ":" + REDIS_PREFIX_USER + username;
        
        if (keyUserDetail == null)
        {
            throw ExceptionFactory.create("403");
        }
        
        return redisRepository.get(keyUserDetail);
    }
    
    public Boolean validateToken(String token, UserDetails userDetails)
    {
        final String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
    
    /*
     * public String refreshToken(String token) { String refreshedToken; try {
     * final Claims claims = getClaimsFromToken(token);
     * claims.put(CLAIM_KEY_CREATED, new Date()); refreshedToken =
     * generateToken(claims); } catch (Exception e) { refreshedToken = null; }
     * return refreshedToken; }
     */
    
    public Long getExpiration()
    {
        return expiration;
    }
    
    public void setExpiration(Long expiration)
    {
        this.expiration = expiration;
    }
    
}